<?php

namespace PhpYes\Modules\Log\Backend;


use PhpYes\Modules\Log\LoggerBackendBase;
use PhpYes\Modules\Log\LogRecordInterface;

class BackendFile extends LoggerBackendBase
{
    private static $logPath2LogHandleArr = array();

    private $pathInfo;
    private $errMsg;

    public function __construct($config)
    {
        parent::__construct($config);
        $path = empty($this->config['path']) ? null : trim($this->config['path']);
        if ($path) {
            $this->pathInfo = pathinfo($path);
        }
    }

    public function __destruct()
    {
        // 这个实例可能打开了多个文件，
        // 如果想只关闭这个实例打开的文件，就需要记录，这样会有些麻烦，
        // 程序中也不太可能单独销毁一个BackendFile实例，
        // 所以，为了省事，就直接关闭所有打开的文件
        foreach (self::$logPath2LogHandleArr as $handle) {
            fclose($handle);
        }
        self::$logPath2LogHandleArr = array();
    }

    /**
     * @param LogRecordInterface $logRecord
     * @return void
     */
    public function logRecord(LogRecordInterface $logRecord)
    {
        $path = empty($this->config['path']) ? null : trim($this->config['path']);
        $dir = empty($this->pathInfo['dirname']) ? null : trim($this->pathInfo['dirname']);
        if (empty($path) || empty($dir)) {
            return;
        }

        $rotate = empty($this->config['rotate']) ? null : trim($this->config['rotate']);
        if ($rotate == 'day' || $rotate == 'month' || $rotate == 'year') {
            if ($rotate == 'day') {
                $t = $logRecord->getDateTime()->format('Y.m.d');
            } elseif ($rotate == 'month') {
                $t = $logRecord->getDateTime()->format('Y.m');
            } else {
                $t = $logRecord->getDateTime()->format('Y');
            }
            $baseName = empty($this->pathInfo['basename']) ? '' : trim($this->pathInfo['basename']);
            $ext = empty($this->pathInfo['extension']) ? '' : trim($this->pathInfo['extension']);
            if ($ext) {
                $path = $dir . DIRECTORY_SEPARATOR . substr($baseName, 0, strlen($baseName) - strlen($ext) - 1) . '.' . $t . '.' . $ext;
            } else {
                $path = $dir . DIRECTORY_SEPARATOR . $baseName . '.' . $t;
            }
        }

        if (empty(self::$logPath2LogHandleArr[$path])) {
            if ($dir && !is_dir($dir)) {
                $this->errMsg = null;
                set_error_handler(array($this, 'customErrorHandler'));
                $status = mkdir($dir, 0777, true);
                restore_error_handler();
                if (false === $status) {
                    throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: ' . $this->errMsg, $dir));
                }
            }

            $this->errMsg = null;
            set_error_handler(array($this, 'customErrorHandler'));
            $handle = fopen($path, 'a');
            restore_error_handler();
            if (!is_resource($handle)) {
                throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errMsg, $path));
            }
            self::$logPath2LogHandleArr[$path] = $handle;
        }

        $handle = self::$logPath2LogHandleArr[$path];

        // ignoring errors here, there's not much we can do about them
        // flock($handle, LOCK_EX);

        // If handle was fopen()ed in append mode,
        // fwrite()s are atomic (unless the size of string exceeds the filesystem's block size, on some platforms, and as long as the file is on a local filesystem).
        // That is, there is no need to flock() a resource before calling fwrite();
        // all of the data will be written without interruption.
        fwrite($handle, trim($logRecord->getLogMessage()) . PHP_EOL);

        // flock($handle, LOCK_UN);
    }

    private function customErrorHandler($code, $msg)
    {
        $this->errMsg = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
    }
}